/*
* Author: Chris Seguin
*
* This software has been developed under the copyleft
* rules of the GNU General Public License. Please
* consult the GNU General Public License for more
* details about use and distribution of this software.
*/
package org.acm.seguin.refactor.type;
import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;
import org.acm.seguin.parser.ast.ASTName;
import org.acm.seguin.pretty.ModifierHolder;
import org.acm.seguin.refactor.AddImportTransform;
import org.acm.seguin.refactor.ComplexTransform;
import org.acm.seguin.refactor.Refactoring;
import org.acm.seguin.refactor.RefactoringException;
import org.acm.seguin.refactor.method.AddConcreteMethod;
import org.acm.seguin.summary.FileSummary;
import org.acm.seguin.summary.MethodSummary;
import org.acm.seguin.summary.PackageSummary;
import org.acm.seguin.summary.TypeDeclSummary;
import org.acm.seguin.summary.TypeSummary;
import org.acm.seguin.summary.VariableSummary;
import org.acm.seguin.summary.query.GetTypeSummary;
/**
* Refactoring that extracts the interface from the dialog
*
*@author Grant Watson
*@created November 27, 2000
*/
public class ExtractInterfaceRefactoring extends Refactoring {
private String m_interfaceName;
private String m_packageName;
private Vector m_summaryList = new Vector();
private ComplexTransform m_complexTransform;
/**
* Constructor for the ExtractInterfaceRefactoring object
*/
protected ExtractInterfaceRefactoring()
{
m_complexTransform = getComplexTransform();
}
/**
* Sets the interface name for the new interface. If the name contains a
* package name, then the package name is also set.
*
*@param interfaceName The new InterfaceName value
*/
public void setInterfaceName(String interfaceName)
{
if (interfaceName.indexOf('.') != -1) {
m_packageName = interfaceName.substring(0, interfaceName.lastIndexOf('.'));
m_interfaceName = interfaceName.substring(interfaceName.lastIndexOf('.') + 1);
}
else {
m_interfaceName = interfaceName;
}
}
/**
* Sets the PackageName attribute of the ExtractInterfaceRefactoring object
*
*@param packageName The new PackageName value
*/
public void setPackageName(String packageName)
{
m_packageName = packageName;
}
/**
* Gets the Description attribute of the ExtractInterfaceRefactoring object
*
*@return The Description value
*/
public String getDescription()
{
return "Extract Interface.";
}
/**
* Gets the ID attribute of the ExtractInterfaceRefactoring object
*
*@return The ID value
*/
public int getID()
{
return EXTRACT_INTERFACE;
}
/**
* Adds a class that will implement the new interface
*
*@param packageName The feature to be added to the ImplementingClass
* attribute
*@param className The feature to be added to the ImplementingClass
* attribute
*/
public void addImplementingClass(String packageName, String className)
{
TypeSummary summary = GetTypeSummary.query(PackageSummary.getPackageSummary(packageName), className);
addImplementingClass(summary);
}
/**
* Adds a feature to the ImplementingClass attribute of the
* ExtractInterfaceRefactoring object
*
*@param summary The feature to be added to the ImplementingClass attribute
*/
public void addImplementingClass(TypeSummary summary)
{
if (summary != null) {
m_summaryList.addElement(summary);
}
}
/**
* Description of the Method
*
*@exception RefactoringException Description of Exception
*/
protected void preconditions() throws RefactoringException
{
if (m_interfaceName == null) {
throw new RefactoringException("Interface name is not specified");
}
if (m_summaryList.size() == 0) {
throw new RefactoringException("Unable to find type to extract interface from");
}
}
/**
* this performs the refactoring
*/
protected void transform()
{
File newFile = createInterfaceFile();
// Add declarations of the common methods to the interface
Vector methodSummaries = getMethodSummaries();
for (int i = 0; i < methodSummaries.size(); i++) {
MethodSummary ms = (MethodSummary) methodSummaries.elementAt(i);
m_complexTransform.add(new AddConcreteMethod(ms));
}
// Add necessary import statements to support parameter and return types
Iterator importTypes = getImportTypes(methodSummaries);
while ((importTypes != null) && (importTypes.hasNext())) {
TypeDeclSummary decl = (TypeDeclSummary) importTypes.next();
TypeSummary type = GetTypeSummary.query(decl);
// If the type is not found, don't attempt to add an import statement
if (type != null) {
m_complexTransform.add(new AddImportTransform(type));
}
}
m_complexTransform.apply(newFile, newFile);
/*
* Delete the backup file for the intermediate new interface file to
* ensure that an 'undo' does not recover it.
*/
newFile = new File(newFile.getAbsolutePath() + ".0");
newFile.delete();
addInterfaceToClasses();
}
/**
* Gets a list of public method summaries that are common to all classes for
* which an interface is being extracted.
*
*@return The MethodSummaries value
*/
private Vector getMethodSummaries()
{
Vector firstClassMethods = new Vector();
// Add all relevant methods from the first class.
TypeSummary ts = (TypeSummary) m_summaryList.elementAt(0);
Iterator methods = ts.getMethods();
while (methods.hasNext()) {
MethodSummary ms = (MethodSummary) methods.next();
ModifierHolder mh = ms.getModifiers();
/*
* Include only public, non-static, non-constructor methods.
* Private and protected methods are not allowed in interfaces and
* methods that are package-protected in an interface need to be
* implemented by public methods in implementing classes (I think).
*/
if (mh.isPublic() && (!ms.isConstructor()) && (!mh.isStatic())) {
// synchronized modifier is not allowed for interfaces.
mh.setSynchronized(false);
firstClassMethods.addElement(ms);
}
}
return commonMethods(firstClassMethods);
}
/**
* Gets a list of the TypeDeclSummaries for the return types and parameters
* in the list of MethodSummaries supplied.
*
*@param methodSummaries Description of Parameter
*@return The ImportTypes value
*/
private Iterator getImportTypes(Vector methodSummaries)
{
HashMap importTypes = new HashMap();
for (int i = 0; i < methodSummaries.size(); i++) {
MethodSummary ms = (MethodSummary) methodSummaries.elementAt(i);
// Add return type to list
TypeDeclSummary retType = ms.getReturnType();
String typeName = retType.getName();
if ((!(typeName.equals("void"))) && (importTypes.get(typeName) == null)) {
importTypes.put(typeName, retType);
}
Iterator params = ms.getParameters();
// Add parameter types to list
while ((params != null) && (params.hasNext())) {
VariableSummary vs = (VariableSummary) params.next();
TypeDeclSummary param = vs.getTypeDecl();
typeName = param.getName();
if (importTypes.get(typeName) == null) {
importTypes.put(typeName, param);
}
}
// Add exception types to list
Iterator exceptions = ms.getExceptions();
while ((exceptions != null) && (exceptions.hasNext())) {
TypeDeclSummary exception = (TypeDeclSummary) exceptions.next();
typeName = exception.getName();
if (importTypes.get(typeName) == null) {
importTypes.put(typeName, exception);
}
}
}
return importTypes.values().iterator();
}
/**
* Adds the name of the newly created interface to the implements clause of
* each class selected for the refactoring.
*/
private void addInterfaceToClasses()
{
for (int i = 0; i < m_summaryList.size(); i++) {
TypeSummary ts = (TypeSummary) m_summaryList.elementAt(i);
FileSummary fileSummary = (FileSummary) ts.getParent();
File file = fileSummary.getFile();
ASTName interfaceName = new ASTName(0);
String currentPackageName = ts.getPackageSummary().getName();
/*
* If the interface package differs from the class package, then
* specify the interface package name
*/
if ((m_packageName.length() > 0) && !(currentPackageName.equals(m_packageName))) {
interfaceName.fromString(m_packageName + "." + m_interfaceName);
}
else {
interfaceName.fromString(m_interfaceName);
}
m_complexTransform.clear();
// Very Important so we don't re-apply the interface transforms
m_complexTransform.add(new AddImplementedInterfaceTransform(interfaceName));
if (!m_packageName.equals(currentPackageName)) {
m_complexTransform.add(new AddImportTransform(interfaceName));
}
m_complexTransform.apply(file, new File(file.getAbsolutePath()));
}
}
/**
* Eliminates methods that don't occurr in all classes and returns the
* resulting Vector of common methods.
*
*@param initialMethods Description of Parameter
*@return Description of the Returned Value
*/
private Vector commonMethods(Vector initialMethods)
{
Vector result = new Vector();
for (int i = 0; i < initialMethods.size(); i++) {
boolean keep = true;
outerloop :
for (int j = 1; j < m_summaryList.size(); j++) {
TypeSummary ts = (TypeSummary) m_summaryList.elementAt(j);
Iterator methods = ts.getMethods();
while (methods.hasNext()) {
MethodSummary ms = (MethodSummary) methods.next();
if (ms.equals((MethodSummary) initialMethods.elementAt(i))) {
continue outerloop;
}
}
keep = false;
}
if (keep) {
MethodSummary ms = (MethodSummary) initialMethods.elementAt(i);
result.addElement(initialMethods.elementAt(i));
}
}
return result;
}
/**
* Creates a new interface file.
*
*@return Description of the Returned Value
*/
private File createInterfaceFile()
{
File newFile = null;
TypeSummary ts = (TypeSummary) m_summaryList.elementAt(0);
PackageSummary ps = ts.getPackageSummary();
if (m_packageName == null) {
m_packageName = ps.getName();
}
CreateNewInterface cni = new CreateNewInterface(ts, m_packageName, m_interfaceName);
try {
newFile = cni.run();
}
catch (RefactoringException re) {
re.printStackTrace();
return null;
}
m_complexTransform.createFile(newFile);
return newFile;
}
}